package com.keba.basic.encrypt.aspect;

import com.keba.basic.encrypt.advice.DecryptRequestAdvice;
import com.keba.basic.encrypt.advice.EncryptResponseAdvice;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Objects;

/**
 * @author ls
 * @since 2023-07-24
 */
@Aspect
@Slf4j
public class SecurityAspect {
    private DecryptRequestAdvice decryptRequestAdvice;
    private EncryptResponseAdvice encryptResponseAdvice;

    public SecurityAspect(DecryptRequestAdvice decryptRequestAdvice, EncryptResponseAdvice encryptResponseAdvice) {
        this.decryptRequestAdvice = decryptRequestAdvice;
        this.encryptResponseAdvice = encryptResponseAdvice;
    }

    @Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.GetMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.PostMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.PutMapping)" +
            "|| @annotation(org.springframework.web.bind.annotation.DeleteMapping)")
    public void httpPointCut() {
    }

    @Around("httpPointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        // 方法名
        String methodName = joinPoint.getSignature().getName();
        // 所有方法参数
        Object[] arguments = joinPoint.getArgs();

        for (int i = 0; i < arguments.length; i++) {
            // 第i个参数
            Object arg = arguments[i];

            // 参数名
            String paramName = getParamName(joinPoint, i);
            // 参数上注解
            Annotation[] annotation = getAnnotation(joinPoint, i);

            Object decrypt = decryptRequestAdvice.decrypt(arg, annotation);

            arguments[i] = decrypt;

            log.debug("Method[{} {}] parameter[{} {}] is decrypted. Final parameter: {}",
                    joinPoint.getSignature().getDeclaringTypeName(),
                    methodName,
                    Objects.isNull(arg) ? null : arg.getClass().getName(),
                    paramName, decrypt);
        }

        // 调用目标方法
        Object object = joinPoint.proceed(arguments);

        // 获取方法对象
        Method method = getMethod(joinPoint.getTarget().getClass(), methodName, arguments);

        Annotation[] methodAnnotations = method.getDeclaredAnnotations();
        Object encrypt = encryptResponseAdvice.encrypt(object, methodAnnotations);

        log.debug("Method[{} {}] returned value[{}] is encrypted. Final value: {}",
                joinPoint.getSignature().getDeclaringTypeName(),
                methodName,
                Objects.nonNull(object) ? object.getClass().getName() : null,
                encrypt);

        //返回目标方法的返回值
        return encrypt;
    }

    private Method getMethod(Class<?> clazz, String methodName, Object[] arguments) throws NoSuchMethodException {
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            if (method.getName().equals(methodName) && method.getParameterCount() == arguments.length) {
                return method;
            }
        }
        return null;
    }


    // Helper method to get parameter name
    private String getParamName(JoinPoint joinPoint, int index) {
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        return paramNames[index];
    }

    // Helper method to get parameter annotation
    private Annotation[] getAnnotation(JoinPoint joinPoint, int index) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getMethod().getParameterAnnotations()[index];
    }
}
